﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace CPDPWEM.Utils
{
    /// <summary>
    /// Base class containing functionality for managing commands. Allows developers to add attributes to methods to
    /// specify methods used for a command.
    /// </summary>
    /// <remarks>
    /// When an instance of the class is created, reflection is used to look for all methods with either the
    /// CommandExecuteAttribute or CommandCanExecuteAttribute. It builds a map of each using the user defined command
    /// names and exposes an interface to call ExecuteCommand(commandName) or CanExecuteCommand(commandName).
    /// 
    /// TODO: Need to figure out how to allow XAML views to bind to these functions.
    /// TODO: Need to figure out how to trigger the associated CanExecuteChanged method.
    /// </remarks>
    public abstract class BaseManageCommands
    {
        protected BaseManageCommands()
        {
            _canExecuteMap = new Dictionary<string, MethodInfo>();
            _executeMap = new Dictionary<string, MethodInfo>();

            BuildCommandMap();
        }

        public bool CanExecuteCommand(string commandName)
        {
            if (!_canExecuteMap.ContainsKey(commandName))
            {
                // If the execute map contains the key but the can-execute does not, return true. Users are not required
                // to provide a can-execute function if the command is always enabled.
                if (_executeMap.ContainsKey(commandName))
                    return true;
                throw new ArgumentException(String.Format("{0}: Unknown command name", commandName));
            }

            return (bool)_canExecuteMap[commandName].Invoke(this, null);
        }

        public void ExecuteCommand(string commandName)
        {
            if (!_executeMap.ContainsKey(commandName))
                throw new ArgumentException(String.Format("{0}: Unknown command name", commandName));
            _executeMap[commandName].Invoke(this, null);
        }

        private void BuildCommandMap()
        {
            BuildMapping<CommandCanExecuteAttribute>(ref _canExecuteMap);
            BuildMapping<CommandExecuteAttribute>(ref _executeMap);
        }

        private void BuildMapping<T>(ref Dictionary<string, MethodInfo> methodMap) where T : BaseCommandAttribute
        {
            // Find all of the methods with the attribute of T attached. Pair the command name and method info together.
            Type myType = GetType();
            MethodInfo[] methods = myType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            var methodsWithAttribute = from methodInfo in methods
                                       let attr = (T)methodInfo.GetCustomAttributes(typeof(T), false).FirstOrDefault()
                                       where attr != null
                                       select new {
                                           attr.CommandName,
                                           methodInfo
                                       };

            // Add the results of the query to the mapping we return to the caller.
            foreach (var method in methodsWithAttribute)
                methodMap[method.CommandName] = method.methodInfo;
        }

        private Dictionary<string, MethodInfo> _canExecuteMap;
        private Dictionary<string, MethodInfo> _executeMap;
    }
}
